Public
Edited
Sep 19
42 forks
142 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const selection = vl.selectPoint();
return vl.markCircle()
.data(cars)
.params(selection)
.encode(
vl.x().fieldQ('Horsepower'),
vl.y().fieldQ('Miles_per_Gallon'),
vl.color().if(selection, vl.fieldO('Cylinders')).value('grey'),
vl.opacity().if(selection, vl.value(0.8)).value(0.1)
)
.render()
}
Insert cell
Insert cell
function plot(selection) {
return vl.markCircle()
.data(cars)
.params(selection)
.encode(
vl.x().fieldQ('Horsepower'),
vl.y().fieldQ('Miles_per_Gallon'),
vl.color().if(selection, vl.fieldO('Cylinders')).value('grey'),
vl.opacity().if(selection, vl.value(0.8)).value(0.1)
)
.width(300)
.height(225);
}
Insert cell
Insert cell
vl.hconcat(
plot(vl.selectPoint()).title('Point (Click)'),
plot(vl.selectInterval()).title('Interval (Drag)')
).render()
Insert cell
Insert cell
plot(
vl.selectPoint().on('mouseover')).title('Point (Mouseover)'
).render()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
genres = uniqueValid(movies, d => d.Major_Genre)
Insert cell
Insert cell
mpaa = ['G', 'PG', 'PG-13', 'R', 'NC-17', 'Not Rated']
Insert cell
Insert cell
{
const selectGenre = vl.selectPoint('Select') // name the selection 'Select'
.fields('Major_Genre') // limit selection to the Major_Genre field
.init({Major_Genre: genres[0]}) // use first genre entry as initial value
.bind(vl.menu(genres)); // bind to a menu of unique genre values
// scatter plot, modify opacity based on genre selection
return vl.markCircle()
.data(movies)
.params(selectGenre)
.encode(
vl.x().fieldQ('Rotten_Tomatoes_Rating'),
vl.y().fieldQ('IMDB_Rating'),
vl.tooltip().fieldN('Title'),
vl.opacity().if(selectGenre, vl.value(0.75)).value(0.05)
)
.render();
}
Insert cell
Insert cell
Insert cell
{
// point-value selection over [Major_Genre, MPAA_Rating] pairs
// use specific hard-wired values as the initial selected values
const selection = vl.selectPoint('Select')
.fields('Major_Genre', 'MPAA_Rating')
.init({Major_Genre: 'Drama', MPAA_Rating: 'R'})
.bind({Major_Genre: vl.menu(genres), MPAA_Rating: vl.radio(mpaa)});
// scatter plot, modify opacity based on selection
return vl.markCircle()
.data(movies)
.params(selection)
.encode(
vl.x().fieldQ('Rotten_Tomatoes_Rating'),
vl.y().fieldQ('IMDB_Rating'),
vl.tooltip().fieldN('Title'),
vl.opacity().if(selection, vl.value(0.75)).value(0.05)
)
.render();
}
Insert cell
Insert cell
Insert cell
{
const brush = vl.selectInterval()
.encodings('x'); // limit selection to x-axis (year) values
// dynamic query histogram
const years = vl.markBar({width: 4})
.data(movies)
.params(brush)
.encode(
vl.x().year('Release_Date').title('Films by Release Year'),
vl.y().count().title(null)
)
.width(600)
.height(50);
// ratings scatter plot
const ratings = vl.markCircle()
.data(movies)
.encode(
vl.x().fieldQ('Rotten_Tomatoes_Rating'),
vl.y().fieldQ('IMDB_Rating'),
vl.tooltip().fieldN('Title'),
vl.opacity().if(brush, vl.value(0.75)).value(0.05)
)
.width(600)
.height(400);

return vl.vconcat(years, ratings).spacing(5).render();
}
Insert cell
Insert cell
Insert cell
Insert cell
vl.markCircle()
.data(movies)
.params(
vl.selectInterval().bind('scales') // bind interval selection to scale domains
)
.encode(
vl.x().fieldQ('Rotten_Tomatoes_Rating'),
vl.y().fieldQ('IMDB_Rating')
.axis({minExtent: 30}), // add min extent to stabilize axis title placement
vl.tooltip(['Title', 'Release_Date', 'IMDB_Rating', 'Rotten_Tomatoes_Rating'])
)
.width(600)
.height(450)
.render()
Insert cell
Insert cell
vl.markCircle()
.data(movies)
.params(
vl.selectInterval().bind('scales').encodings('x') // bind to x-axis scale only
)
.encode(
vl.x().fieldQ('Rotten_Tomatoes_Rating'),
vl.y().fieldQ('IMDB_Rating').axis({minExtent: 30}),
vl.tooltip(['Title', 'Release_Date', 'IMDB_Rating', 'Rotten_Tomatoes_Rating'])
)
.width(600)
.height(450)
.render()
Insert cell
Insert cell
Insert cell
Insert cell
{
const brush = vl.selectInterval().encodings('x');
const x = vl.x().fieldT('date').title(null);
const base = vl.markArea()
.encode(x, vl.y().fieldQ('price'))
.width(700);
return vl.data(sp500)
.vconcat(
base.encode(x.scale({domain: brush})),
base.params(brush).height(60)
)
.render();
}
Insert cell
Insert cell
Insert cell
Insert cell
{
const hover = vl.selectPoint()
.on('mouseover') // select on mouseover
.toggle(false) // do not toggle on shift-hover
.nearest(true); // select nearest point to mouse cursor
const click = vl.selectPoint();
// combine to select points in either selection
// empty selections should match nothing
const hoverOrClick = vl.or(click.empty(false), hover.empty(false));
// scatter plot encodings shared by all marks
const plot = vl.markCircle().encode(
vl.x().fieldQ('Rotten_Tomatoes_Rating'),
vl.y().fieldQ('IMDB_Rating')
);
// shared base for new layers
const base = plot.transform(
vl.filter(hoverOrClick)
);
// mark properties for new layers
const halo = {size: 100, stroke: 'firebrick', strokeWidth: 1};
const label = {dx: 4, dy: -8, align: 'right'};
const white = {stroke: 'white', strokeWidth: 2};

// layer scatter plot points, halo annotations, and title labels
return vl.data(movies)
.layer(
plot.params(hover, click),
base.markPoint(halo),
base.markText(label, white).encode(vl.text().fieldN('Title')),
base.markText(label).encode(vl.text().fieldN('Title'))
)
.width(600)
.height(450)
.render();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const brush = vl.selectInterval()
.resolve('global'); // resolve all selections to a single global instance
const legend = vl.selectPoint()
.fields('Cylinders')
.bind('legend'); // bind to interactions with the color legend
const brushAndLegend = vl.and(brush, legend);
return vl.markCircle()
.data(cars)
.params(brush, legend)
.encode(
vl.x().fieldQ(vl.repeat('column')),
vl.y().fieldQ(vl.repeat('row')),
vl.color().if(brushAndLegend, vl.fieldO('Cylinders')).value('grey'),
vl.opacity().if(brushAndLegend, vl.value(0.8)).value(0.1)
)
.width(140)
.height(140)
.repeat({
column: ['Acceleration', 'Horsepower', 'Miles_per_Gallon'],
row: ['Miles_per_Gallon', 'Horsepower', 'Acceleration']
})
.render();
}
Insert cell
Insert cell
Insert cell
{
const brush = vl.selectInterval().encodings('x').resolve('intersect');
const hist = vl.markBar().encode(
vl.x().fieldQ(vl.repeat('row'))
.bin({maxbins: 100, minstep: 1}) // up to 100 bins, but no smaller than 1 unit
.axis({format: 'd', titleAnchor: 'start'}), // integer format, left-aligned title
vl.y().count().title(null) // no y-axis title
);
return vl.layer(
hist.params(brush).encode(vl.color().value('lightgrey')),
hist.transform(vl.filter(brush))
)
.width(900).height(100)
.repeat({row: ['delay', 'distance', 'time']})
.data(flights)
.transform(
vl.calculate('datum.delay < 180 ? datum.delay : 180').as('delay'), // clamp delays > 3 hours
vl.calculate('hours(datum.date) + minutes(datum.date) / 60').as('time') // fractional hours
)
.config({view: {stroke: null}}) // no outline
.render();
}
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more